﻿using System;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;

namespace VMExplorer.Controls
{
  partial class MemoryDump : UserControl
	{
		#region Private fields

		private VMEDocument _doc = null;

		private bool _isAutoWidth = false;
		private int _bytesPerRow;

		private Font _bytesFont;
		private int _charWidth = 1;
		private int _charHeight = 1;

    private MemoryAddress _startAddress;
    private ByteBuffer _byteBuffer;

		#endregion

		#region Construction

		public MemoryDump ()
    {
      InitializeComponent ();

      this._textArea.Parent = this;

			StartAddress = new MemoryAddress (0x00400);
			BytesFont = new System.Drawing.Font ("Consolas", 9.0F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(0)));
			BytesForeColor = System.Drawing.Color.Black;
			comboBox1.SelectedItem = "16";
		}

		#endregion

		#region Public properties

		public VMEDocument Document
    {
      get { return _doc; }
      set
      {
        if (_doc != null)
        {
          _doc.SelectedAddressChanged -= _doc_SelectedAddressChanged;
          _doc.DataUpdated -= _doc_DataUpdated;
        }
        _doc = value;
        if (_doc != null)
        {
          _doc.SelectedAddressChanged += _doc_SelectedAddressChanged;
          _doc.DataUpdated += _doc_DataUpdated;
        }
      }
    }

		public MemoryAddress StartAddress 
    {
      get { return _startAddress; }
      set
      {
        _startAddress = value;
        _byteBuffer = null;
        if (_startAddress != MemoryAddress.Invalid)
          FillByteBuffer ();
      }
    }

		public int BytesPerRow 
		{ 
			get { return _bytesPerRow; }
			set
			{
				_bytesPerRow = Math.Max (1, value);

				_widthBytes = _bytesPerRow * _widthOneByte + (_bytesPerRow - 1) * InterByteWidth + 2 * _charWidth;

				_vScrollBar.SmallChange = _bytesPerRow;
				_vScrollBar.LargeChange = VisibleBytes - _bytesPerRow;

        FillByteBuffer ();
			}
		}

		public int VisibleBytes
		{
			get { return BytesPerRow * (_textArea.Height / _charHeight); }
		}

		public Font BytesFont 
		{ 
			get { return _bytesFont; }
			set
			{
				_bytesFont = value;
				using (Graphics g = CreateGraphics ())
				{
          String test = "0x123456789ABCDEF ";
          Size size = g.MeasureString (test, _bytesFont).ToSize ();
          _charWidth = Math.Max (1, size.Width / test.Length);
					_charHeight = Math.Max (1, size.Height);

          test = "0x12345678";
          _widthAddress = 1 + g.MeasureString (test, _bytesFont).ToSize ().Width + _charWidth;

					test = "00";
					_widthOneByte = g.MeasureString (test, _bytesFont).ToSize ().Width;

					test = ".";
					_widthOneChar = g.MeasureString (test, _bytesFont).ToSize ().Width;

					if (_isAutoWidth)
					{
						BytesPerRow = ComputeBytesPerRow ();
					}
				}
			}
		}

		public Color BytesForeColor { get; set; }

		public int CharWidth { get { return _charWidth; } }
		public int CharHeight { get { return _charHeight; } }

    public ByteBuffer ByteBuffer { get { return _byteBuffer; } }

    private int _widthAddress;
		private int _widthOneByte;
		private int _widthOneChar;
    private int _widthBytes;

    public int AddressColumnWidth { get { return _widthAddress; } }
		public int BytesColumnWidth { get { return _widthBytes; } }
		public int OneByteWidth { get { return _widthOneByte; } }
		public int InterByteWidth { get { return _widthOneChar/3; } }

    #endregion

    #region Utils

    private void FillByteBuffer()
    {
      if (Document == null) return;
			_byteBuffer = Document.ReadByteBuffer (StartAddress, VisibleBytes);
      Refresh ();
    }

		private int ComputeBytesPerRow ()
		{
			int availableWidth = _textArea.Width - _widthAddress - 1;
			
			// _??_??_ ... ??_._?? ... ?_
			// Estimated string length = (1 + 3*N + 1 + N + 1) * CharWidth + 1
			int nbBytes = ((availableWidth - 1) / _charWidth - 3) / 4;

			using (Graphics g = CreateGraphics ())
			{
				StringBuilder sb = new StringBuilder ();
				do
				{
					int width = nbBytes * (_widthOneByte + _widthOneChar) + (nbBytes-1) * InterByteWidth + 4 * _charWidth + 1;

					if (width < availableWidth)
						break;

					nbBytes--;
				} while (nbBytes>1);

			}
			return nbBytes;
		}

    #endregion


    void _doc_DataUpdated (object sender, EventArgs e)
    {
      Invoke ((MethodInvoker)delegate ()
      {
        Refresh ();
      });
    }

    void _doc_SelectedAddressChanged (object sender, SelectedAddressChangedEventArgs e)
    {
      StartAddress = MemoryAddress.Invalid;
      if (_doc.SelectedAddress != MemoryAddress.Invalid)
      {
        StartAddress = _doc.SelectedAddress.LowerPageAligned;
      }
    }

    private void _vScrollBar_Scroll (object sender, ScrollEventArgs e)
    {
			bool initScrollBar = true;
			switch (e.Type)
			{
			case ScrollEventType.SmallDecrement:
          if (StartAddress != MemoryAddress.Null)
          {
            if (StartAddress - _vScrollBar.SmallChange > MemoryAddress.Null)
              StartAddress -= _vScrollBar.SmallChange;
            else
              StartAddress = MemoryAddress.Null;
          }
          else 
          {
            initScrollBar = false; 
          }
				break;

			case ScrollEventType.SmallIncrement:
				StartAddress += _vScrollBar.SmallChange;
				break;

			case ScrollEventType.LargeDecrement:
				StartAddress -= _vScrollBar.LargeChange;
				break;

			case ScrollEventType.LargeIncrement:
				StartAddress += _vScrollBar.LargeChange;
				break;

			case ScrollEventType.ThumbTrack:
				int step = _vScrollBar.Maximum / 32;
				int diff = _vScrollBar.Value - (_vScrollBar.Maximum / 2);
				if (diff != 0)
				{
					int sign = Math.Sign (diff);
					diff = Math.Abs (diff);

					int speed = (diff + step -1) / step;

					_scrollTimer.Interval = 500 / speed;
					_scrollTimer.Tag = sign*speed;

					_scrollTimer.Start ();
				}
				initScrollBar = false;
				break;

			case ScrollEventType.EndScroll:
				_scrollTimer.Stop ();
				break;

			case ScrollEventType.ThumbPosition:
			case ScrollEventType.First:
			case ScrollEventType.Last:
			default:
				break;
			}

			if (initScrollBar)
			{
				e.NewValue = _vScrollBar.Maximum / 2;
			}

    }

		private void _scrollTimer_Tick (object sender, EventArgs e)
		{
			int speed = (int)_scrollTimer.Tag;
			if (speed == 0) return;
			StartAddress += _vScrollBar.SmallChange*speed;
		}

		private void MemoryDump_SizeChanged (object sender, EventArgs e)
		{
			if (_isAutoWidth)
			{
				BytesPerRow = ComputeBytesPerRow ();
			}
		}

		private void comboBox1_SelectedValueChanged (object sender, EventArgs e)
		{
			string item = comboBox1.SelectedItem as string;
			if (item == null) return;
			_isAutoWidth = (item == "Auto");

			if(_isAutoWidth)
			{
				BytesPerRow = ComputeBytesPerRow();
			}
			else
			{
				BytesPerRow = int.Parse (item);
			}
		}

  }
}
